/**
 * yeganaaa@163.com
 * author: (مۇختەرجان مەخمۇت) muhtaer mahmood
 * 10/13/2019, 1:40:26 PM GMT+06:00
 */

import {PromiseCompleter, RpcBridge} from "rpc_dream"
import { IClientInterfaceBase } from "./IClientInterfaceBase"
import { v4 as uuid } from "uuid"
import { IRpcSocketServer, RpcBridgeEvents } from "rpc_dream/bin/Rpc"

export abstract class RpcState {
    public key: string = uuid()
    public owners: RpcClientSession<any, any, any, any>[] = []
}

export abstract class RpcClientSession<LOCAL_INTERFACE, REMOTE_INTERFACE extends IClientInterfaceBase, CLIENT_STATE extends RpcState, RPC_SERVER extends RpcServer<RPC_SERVER, LOCAL_INTERFACE, REMOTE_INTERFACE, CLIENT_STATE>> {
    public id: string = uuid()
    public state: CLIENT_STATE = null!
    public bridge: RpcBridge<LOCAL_INTERFACE, REMOTE_INTERFACE> = null!
    public server: RPC_SERVER = null!
    public abstract getLocalInterfaceImplementation(): Promise<LOCAL_INTERFACE>
    public abstract createState(): Promise<CLIENT_STATE>
    public abstract restoreState(state: CLIENT_STATE): Promise<void>
    public onReadyCompleter = new PromiseCompleter<void>()
    public async onReady(): Promise<void> { }
    public onDisconnected(): void { }
    public onError(error: Error) { }
    constructor() {
        this.onReadyCompleter.build()
    }
}

export abstract class RpcServer<RPC_SERVER extends RpcServer<RPC_SERVER, LOCAL_INTERFACE, REMOTE_INTERFACE, CLIENT_STATE>, LOCAL_INTERFACE, REMOTE_INTERFACE extends IClientInterfaceBase, CLIENT_STATE extends RpcState> {
    public states: Map<string, CLIENT_STATE> = new Map()
    public clients: Map<string, RpcClientSession<LOCAL_INTERFACE, REMOTE_INTERFACE, CLIENT_STATE, RPC_SERVER>> = new Map()
    private expirationTasks: Map<string, NodeJS.Timeout> = new Map()
    protected onRpcBridgeConfiguration: (bridge: RpcBridge<LOCAL_INTERFACE, REMOTE_INTERFACE>) => void = bridge => { }

    public constructor(public server: IRpcSocketServer<any>, public expirationTimeMilliSeconds: number = 1000 * 60 * 60) {
        server.onLaunch = () => {
            this.onLaunch()
        }
        server.onClientConnected = async clientSocket => {
            const client = await this.createClientSessionInstance()
            client.server = this as any
            this.clients.set(client.id, client)
            const localInterface = await client.getLocalInterfaceImplementation()
            const bridge = new RpcBridge<LOCAL_INTERFACE, REMOTE_INTERFACE>(clientSocket, localInterface)
            this.onRpcBridgeConfiguration(bridge)
            client.bridge = bridge
            const oldEvents = bridge.events
            bridge.events = new RpcBridgeEvents(
                async () => {
                    if (client.bridge.remoteInterface.stateModule == null) {
                        client.bridge.remoteInterface.sendErrorMessage("Rpc StateModule is not implemented in this client, please implement 'yourClientInterface.stateModule' interface, this connection will be close immediately.")
                        setTimeout(() => {
                            bridge.bridgeHost.close(() => {
                                console.log("Unimplemented RpcClientState client now disconnected.")
                            })
                        }, 1000)
                    }
                    else {
                        let key = await client.bridge.remoteInterface.stateModule.getStateKey()
                        if (key == null) key = ""
                        const containingState = this.states.get(key)
                        if (containingState == null) {
                            client.state = await client.createState()
                            await client.bridge.remoteInterface.stateModule.setStateKey(client.state.key)
                            this.states.set(client.state.key, client.state)
                        }
                        else {
                            await client.restoreState(containingState)
                            const expiritionTaskId = this.expirationTasks.get(client.state.key)
                            if (expiritionTaskId != null) clearTimeout(expiritionTaskId)
                            await client.bridge.remoteInterface.stateModule.stateRestored(containingState.key)
                        }
                        client.state.owners.push(client)
                        await client.onReady()
                        client.onReadyCompleter.resolve()
                    }

                    oldEvents.onReady()
                },
                async message => {
                    await client.onReadyCompleter.completer
                    oldEvents.onMessageReceived(message)
                },
                error => {
                    oldEvents.onError(error)
                    client.onError(error)
                },
                () => {
                    if (client.state != null) {
                        client.state.owners = client.state.owners.filter(item => item.id != client.id)
                        if (client.state.owners.length == 0) {
                            this.expirationTasks.set(client.state.key, setTimeout(() => {
                                this.processExpiredStateObject(client.state)
                            }, expirationTimeMilliSeconds))
                        }
                    }
                    this.clients.delete(client.id)
                    oldEvents.onClosed()
                    client.onDisconnected()
                }
            )
            await bridge.initialize()
        }
        server.onError = error => {
            this.onError(error)
        }
        server.onShutDown = () => {
            console.log("Server ")
        }
        server.onShutDown = () => {
            this.onShutDown()
        }
    }
    protected onLaunch(): void {
        console.log("Rpc server launch successfully!")
    }
    protected abstract createClientSessionInstance(): Promise<RpcClientSession<LOCAL_INTERFACE, REMOTE_INTERFACE, CLIENT_STATE, RPC_SERVER>>
    protected async processExpiredStateObject(state: CLIENT_STATE): Promise<void> {
        const containedState = this.states.delete(state.key)
    }
    protected onError(error: Error) {
        console.error(error)
    }
    protected onShutDown() {
        console.log("Rpc server shutting down.")
    }
}

